/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wandadoo.fr
 * Copyright (C) 1992-2020 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#ifndef BOARD_ITEM_STRUCT_H
#define BOARD_ITEM_STRUCT_H


#include <eda_item.h>
#include <eda_units.h>
#include <convert_to_biu.h>
#include <gr_basic.h>
#include <layers_id_colors_and_visibility.h>
#include <geometry/geometry_utils.h>

#include <wx/msgdlg.h>

class BOARD;
class BOARD_ITEM_CONTAINER;
class SHAPE_POLY_SET;
class PCB_BASE_FRAME;
class SHAPE;
class PCB_GROUP;

/**
 * The set of shapes for PCB graphics and tracks and footprint graphics in the .m_Shape member
 */
enum PCB_SHAPE_TYPE_T
{
    S_SEGMENT = 0,  ///< usual segment : line with rounded ends
    S_RECT,         ///< segment with non rounded ends
    S_ARC,          ///< Arcs (with rounded ends)
    S_CIRCLE,       ///< ring
    S_POLYGON,      ///< polygon (not yet used for tracks, but could be in microwave apps)
    S_CURVE,        ///< Bezier Curve
    S_LAST          ///< last value for this list
};

static inline wxString PCB_SHAPE_TYPE_T_asString( PCB_SHAPE_TYPE_T a )
{
    switch( a )
    {
    case S_SEGMENT: return "S_SEGMENT";
    case S_RECT:    return "S_RECT";
    case S_ARC:     return "S_ARC";
    case S_CIRCLE:  return "S_CIRCLE";
    case S_POLYGON: return "S_POLYGON";
    case S_CURVE:   return "S_CURVE";
    case S_LAST:    return "S_LAST";    // Synthetic value, but if we come across it we're
                                        // going to want to know.
    }

    return wxEmptyString;  // Just to quiet GCC.
};


/**
 * A base class for any item which can be embedded within the #BOARD container class, and
 * therefore instances of derived classes should only be found in Pcbnew or other programs
 * that use class #BOARD and its contents.
 */
class BOARD_ITEM : public EDA_ITEM
{
public:
    BOARD_ITEM( BOARD_ITEM* aParent, KICAD_T idtype ) :
            EDA_ITEM( aParent, idtype ),
            m_layer( F_Cu ),
            m_group( nullptr )
    {
    }

    void SetParentGroup( PCB_GROUP* aGroup ) { m_group = aGroup; }
    PCB_GROUP* GetParentGroup() const { return m_group; }

    // Do not create a copy constructor & operator=.
    // The ones generated by the compiler are adequate.
    int GetX() const
    {
        wxPoint p = GetPosition();
        return p.x;
    }

    int GetY() const
    {
        wxPoint p = GetPosition();
        return p.y;
    }

    /**
     * This defaults to the center of the bounding box if not overridden.
     *
     * @return center point of the item
     */
    virtual wxPoint GetCenter() const
    {
        return GetBoundingBox().GetCenter();
    }

    void SetX( int aX )
    {
        wxPoint p( aX, GetY() );
        SetPosition( p );
    }

    void SetY( int aY )
    {
        wxPoint p( GetX(), aY );
        SetPosition( p );
    }

    /**
     * Returns information if the object is derived from BOARD_CONNECTED_ITEM.
     *
     * @return True if the object is of BOARD_CONNECTED_ITEM type, false otherwise.
     */
    virtual bool IsConnected() const
    {
        return false;
    }

    /**
     * @return true if the object is on any copper layer, false otherwise.
     */
    virtual bool IsOnCopperLayer() const
    {
        return IsCopperLayer( GetLayer() );
    }

    /**
     * A value of wxPoint(0,0) which can be passed to the Draw() functions.
     */
    static wxPoint ZeroOffset;

    /**
     * Some pad shapes can be complex (rounded/chamfered rectangle), even without considering
     * custom shapes.  This routine returns a COMPOUND shape (set of simple shapes which make
     * up the pad for use with routing, collision determination, etc).
     *
     * @note This list can contain a SHAPE_SIMPLE (a simple single-outline non-intersecting
     * polygon), but should never contain a SHAPE_POLY_SET (a complex polygon consisting of
     * multiple outlines and/or holes).
     *
     * @param aLayer in case of items spanning multiple layers, only the shapes belonging to aLayer
     *               will be returned. Pass UNDEFINED_LAYER to return shapes for all layers.
     */
    virtual std::shared_ptr<SHAPE> GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER ) const;

    BOARD_ITEM_CONTAINER* GetParent() const { return (BOARD_ITEM_CONTAINER*) m_parent; }

    /**
     * Return the primary layer this item is on.
     */
    virtual PCB_LAYER_ID GetLayer() const { return m_layer; }

    /**
     * Return a std::bitset of all layers on which the item physically resides.
     */
    virtual LSET GetLayerSet() const { return LSET( m_layer ); }
    virtual void SetLayerSet( LSET aLayers )
    {
        wxFAIL_MSG( "Attempted to SetLayerSet() on a single-layer object." );

        // Derived classes which support multiple layers must implement this
    }

    /**
     * Set the layer this item is on.
     *
     * This method is virtual because some items (in fact: class DIMENSION)
     * have a slightly different initialization.
     *
     * @param aLayer The layer number.
     */
    virtual void SetLayer( PCB_LAYER_ID aLayer )
    {
        m_layer = aLayer;
    }

    /**
     * Create a copy of a of this #BOARD_ITEM.
     */
    virtual BOARD_ITEM* Duplicate() const
    {
        EDA_ITEM* dupe = Clone();
        const_cast<KIID&>( dupe->m_Uuid ) = KIID();

        return static_cast<BOARD_ITEM*>( dupe );
    }

    /**
     * Swap data between \a aItem and \a aImage.
     *
     * \a aItem and \a aImage should have the same type.
     *
     * Used in undo and redo commands to swap values between an item and its copy.
     * Only values like layer, size .. which are modified by editing are swapped.
     *
     * @param aImage the item image which contains data to swap.
     */
    virtual void SwapData( BOARD_ITEM* aImage );

    /**
     * Test to see if this object is on the given layer.
     *
     * Virtual so objects like #PAD, which reside on multiple layers can do their own form
     * of testing.
     *
     * @param aLayer The layer to test for.
     * @return true if on given layer, else false.
     */
    virtual bool IsOnLayer( PCB_LAYER_ID aLayer ) const
    {
        return m_layer == aLayer;
    }

    /**
     * Test to see if this object is a track or via (or microvia).
     *
     * @return true if a track or via, else false.
     */
    bool IsTrack() const
    {
        return ( Type() == PCB_TRACE_T ) || ( Type() == PCB_VIA_T );
    }

    /**
     * @return true if the object is locked, else false.
     */
    virtual bool IsLocked() const
    {
        return GetState( LOCKED );
    }

    /**
     * Modify the 'lock' status for of the item.
     */
    virtual void SetLocked( bool aLocked )
    {
        SetState( LOCKED, aLocked );
    }

    /**
     * Delete this object after removing from its parent if it has one.
     */
    void DeleteStructure();

    /**
     * Convert the enum #PCB_SHAPE_TYPE_T integer value to a wxString.
     */
    static wxString ShowShape( PCB_SHAPE_TYPE_T aShape );

    /**
     * Move this object.
     *
     * @param aMoveVector the move vector for this object.
     */
    virtual void Move( const wxPoint& aMoveVector )
    {
        wxFAIL_MSG( wxString::Format( wxT( "virtual BOARD_ITEM::Move called for %s" ),
                                      GetClass() ) );
    }

    void Move( const VECTOR2I& aMoveVector )
    {
        Move( wxPoint( aMoveVector.x, aMoveVector.y ) );
    }

    /**
     * Rotate this object.
     *
     * @param aRotCentre the rotation point.
     * @param aAngle the rotation angle in 0.1 degree.
     */
    virtual void Rotate( const wxPoint& aRotCentre, double aAngle )
    {
        wxMessageBox( wxT( "virtual BOARD_ITEM::Rotate used, should not occur" ), GetClass() );
    }

    void Rotate( const VECTOR2I& aRotCentre, double aAngle )
    {
        Rotate( wxPoint( aRotCentre.x, aRotCentre.y ), aAngle );
    }

    /**
     * Flip this object, i.e. change the board side for this object.
     *
     * @param aCentre the rotation point.
     * @param aFlipLeftRight mirror across Y axis instead of X (the default).
     */
    virtual void Flip( const wxPoint& aCentre, bool aFlipLeftRight )
    {
        wxMessageBox( wxT( "virtual BOARD_ITEM::Flip used, should not occur" ), GetClass() );
    }

    void Flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
    {
        Flip( wxPoint( aCentre.x, aCentre.y ), aFlipLeftRight );
    }

    /**
     * Return the #BOARD in which this #BOARD_ITEM resides, or NULL if none.
     */
    virtual BOARD* GetBoard() const;

    /**
     * Return the name of the PCB layer on which the item resides.
     *
     * @return the layer name associated with this item.
     */
    wxString GetLayerName() const;

    virtual void ViewGetLayers( int aLayers[], int& aCount ) const override;

    /**
     * Convert the item shape to a closed polygon.
     *
     * Used in filling zones calculations.  Circles and arcs are approximated by segments.
     *
     * @param aCornerBuffer a buffer to store the polygon.
     * @param aClearanceValue the clearance around the pad.
     * @param aError the maximum deviation from true circle.
     * @param aErrorLoc should the approximation error be placed outside or inside the polygon?
     * @param ignoreLineWidth used for edge cut items where the line width is only
     *                        for visualization.
     */
    virtual void TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
                                                       PCB_LAYER_ID aLayer, int aClearanceValue,
                                                       int aError, ERROR_LOC aErrorLoc,
                                                       bool ignoreLineWidth = false ) const;

    struct ptr_cmp
    {
        bool operator() ( const BOARD_ITEM* a, const BOARD_ITEM* b ) const;
    };

protected:
    /**
     * Return a string (to be shown to the user) describing a layer mask. The BOARD is needed
     * because layer names are customizable.
     */
    virtual wxString layerMaskDescribe() const;

    PCB_LAYER_ID    m_layer;
    PCB_GROUP*      m_group;
};

#ifndef SWIG
DECLARE_ENUM_TO_WXANY( PCB_LAYER_ID );
#endif


/**
 * A singleton item of this class is returned for a weak reference that no longer exists.
 *
 * Its sole purpose is to flag the item as having been deleted.
 */
class DELETED_BOARD_ITEM : public BOARD_ITEM
{
public:
    DELETED_BOARD_ITEM() :
        BOARD_ITEM( nullptr, NOT_USED )
    {}

    wxString GetSelectMenuText( EDA_UNITS aUnits ) const override
    {
        return _( "(Deleted Item)" );
    }

    wxString GetClass() const override
    {
        return wxT( "DELETED_BOARD_ITEM" );
    }

    // pure virtuals:
    void SetPosition( const wxPoint& ) override {}
    wxPoint GetPosition() const override { return wxPoint(0, 0); }

    static DELETED_BOARD_ITEM* GetInstance()
    {
        static DELETED_BOARD_ITEM* item = nullptr;

        if( !item )
            item = new DELETED_BOARD_ITEM();

        return item;
    }

#if defined(DEBUG)
    void Show( int , std::ostream&  ) const override {}
#endif
};


#endif /* BOARD_ITEM_STRUCT_H */
